package pl.smath.parser.text;

import java.util.Vector;

import pl.smath.expression.ExponentiationTerm;
import pl.smath.expression.FractionTerm;
import pl.smath.expression.NumberTerm;
import pl.smath.expression.ProductTerm;
import pl.smath.expression.SumTerm;
import pl.smath.expression.SymbolTerm;
import pl.smath.expression.Term;


/**
 * Parsuje wyrazenie dostarczone jako tekst
 * @author klangner
 *
 */
public class TextParser {
	
	/**
	 * tokenizer do pobierania token z wejscia
	 */
	private Tokenizer	tokenizer;

	
	/**
	 * Parse given expression
	 * @return Root node
	 */
	public Term parse(String exp){

		tokenizer = new Tokenizer(exp);
		return sum();
	}
	
	
	/**
	 * Pobranie sumy wyrazen z wejscia
	 * @return
	 */
	private Term sum(){
		
		Vector<Term> terms = new Vector<Term>();

		terms.add(multi());
	    
	    while(tokenizer.hasMoreTokens()){
	    	
	    	Token token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '+'){
	    		terms.add(multi());
	    	}
	    	else if(token.getText().charAt(0) == '-'){
	    		Term term = multi();
	    		term.setNegative(!term.isNegative());
	    		terms.add(term);
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
	    
	    if(terms.size() > 1){
	    	SumTerm term = new SumTerm();
	    	term.addTerms(terms);
	    	return term;
	    }
	    else{
	    	return terms.get(0);
	    }
	}

	
	/**
	 * Pobranie iloczynu wyrazen z wejscia
	 * @return
	 */
	private Term multi()
	{
	    Vector<Term> terms = new Vector<Term>();

	    terms.add(fraction());

	    while(tokenizer.hasMoreTokens()){
	    	
	    	Token token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '*'){
	    		terms.add(fraction());
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
	    
	    if(terms.size() > 1){
	    	ProductTerm term = new ProductTerm();
	    	term.addTerms(terms);
	    	return term;
	    }
	    else{
	    	return terms.get(0);
	    }
	    
	}
	
	
	/**
	 * Pobranie iloczynu wyrazen z wejscia
	 * @return
	 */
	private Term fraction()
	{
	    Term denominator;
	    Term term;

	    term = pow();

	    while(tokenizer.hasMoreTokens()){
	    	
	    	Token token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '/'){
	    		denominator = pow();
	    		term = new FractionTerm(term, denominator);
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
    	
    	return term;
	}
	
	
	/**
	 * Pobranie elementu a^b
	 * @return
	 */
	private Term pow()
	{
	    Term exponent;
	    Term term;
	    Token 	token;
	    boolean isNegative = false;

		token = tokenizer.nextToken();
		
		if(token != null){
			if(token.getText().charAt(0) == '-')
				isNegative = true;
			else
				tokenizer.pushback(token);
		}

		term = element();

	    while(tokenizer.hasMoreTokens()){
	    	
	    	token = tokenizer.nextToken();
	    	if(token.getText().charAt(0) == '^'){
	    		exponent = unary();
	    		term = new ExponentiationTerm(term, exponent);
	    	}
	    	else{
	    		//push back token
	    		tokenizer.pushback(token);
	    		break;
	    	}
	    }
    	
    	term.setNegative(isNegative);
    	
    	return term;
	}


	/**
	 * Pobranie elementu poprzedzonego znakiem -
	 * @return
	 */
	private Term unary(){
	    
		Token 	token = tokenizer.nextToken();
		
		if(token != null){
			if(token.getText().charAt(0) == '-'){
				Term 	term = element();
				term.setNegative(true);
				return term;
			}
			else{
				tokenizer.pushback(token);
				return element();
			}
		}
		
		return null;
	}

	
	/**
	 * Pobranie elementu z wejscia
	 * Elementem jest liczba, symbol lub wyrazenie w nawiasach
	 */
	private Term element()
	{
		Token token = tokenizer.nextToken();
		
		if(token instanceof NumberToken)
			return new NumberTerm( token.getText() );
		else if(token instanceof SymbolToken)
			return new SymbolTerm( token.getText() );
		else{
			if(token.getText().charAt(0) == '('){

				Term term = sum();
				token = tokenizer.nextToken();
				if(token.getText().charAt(0) != ')')
					return null;
				else
					return term;
			}
		}
		
		return null;
		
	}


}
